package net.vinote.sosa.web.interceptor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 表单防重复提交
 * 
 * @author Seer
 * @version DuplicateSubmitTokenCheckInterceptor.java, v 0.1 2015年9月24日
 *          下午3:50:08 Seer Exp.
 */
public class DuplicateSubmitTokenCheckInterceptor extends
		HandlerInterceptorAdapter {
	private Logger logger = LogManager
			.getLogger(DuplicateSubmitTokenCheckInterceptor.class);
	private static final String FORM_TOKEN_FIELD_NAME = "_form_token";
	/** 防重复提交缓存Key */
	private static final String DUPLICATE_TOKEN_OF_FROMS = "_duplicate_in_session_token";

	/** 防重复URL */
	private List<String> blackExtensions = new ArrayList<String>();

	/** 重复提交时限 */
	private int timeout;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.web.servlet.handler.HandlerInterceptorAdapter#preHandle
	 * (javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse, java.lang.Object)
	 */
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {

		String token = getFormToken(request);
		// 当前URL是否需要检查
		if (isSkipped(request.getRequestURI()) || StringUtils.isEmpty(token)) {
			if (logger.isDebugEnabled()) {
				logger.debug("当前URL不需要DuplicateSubmitTokenCheckInterceptor校验，跳出。URL="
						+ request.getRequestURL());
			}
			return true;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("当前URL需要DuplicateSubmitTokenCheckInterceptor校验，继续校验。URL="
					+ request.getRequestURL());
		}

		TokenUnit initUnit = new TokenUnit(token, 0, false);
		TokenUnit tokenUnit = getAndSetTokenUnit(request, initUnit);
		// 接收新创建TokenUnit、之前的请求已完成且超过时限的请求
		if (tokenUnit == initUnit
				|| (tokenUnit.finishFlag && System.currentTimeMillis() > tokenUnit.expire)) {
			return true;
		} else {
			throw new RuntimeException("表单重复提交，令牌[" + token + "]");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.web.servlet.handler.HandlerInterceptorAdapter#postHandle
	 * (javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse, java.lang.Object,
	 * org.springframework.web.servlet.ModelAndView)
	 */
	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		String token = getFormToken(request);
		if (isSkipped(request.getRequestURI()) || StringUtils.isEmpty(token)) {
			return;
		}
		TokenUnit unit = getAndSetTokenUnit(request, null);
		if (unit != null) {
			unit.expire = System.currentTimeMillis() + timeout;
			unit.finishFlag = true;
		}
	}

	/**
	 * 获得目前session中的Token列表。
	 *
	 * @return 返回的Map中以表单的token为键，TokenUnit对象为值
	 */
	@SuppressWarnings("unchecked")
	private TokenUnit getAndSetTokenUnit(HttpServletRequest request,
			TokenUnit tokenUnit) {
		TokenUnit unit = null;
		String token = getFormToken(request);
		HttpSession session = request.getSession();
		// 经SOFA封装最终调用SessionRequestContextImpl中的getSession方法导致每次产生一个新的session对象
		// 此处只能用Session ID作为锁对象
		synchronized (session.getId().intern()) {
			Map<String, TokenUnit> tokensInSession = (Map<String, TokenUnit>) session
					.getAttribute(DUPLICATE_TOKEN_OF_FROMS);
			if (tokensInSession == null) {
				tokensInSession = new HashMap<String, TokenUnit>();
				session.setAttribute(DUPLICATE_TOKEN_OF_FROMS, tokensInSession);
			} else {
				clearExpireToken(tokensInSession);
			}
			unit = tokensInSession.get(token);
			if (unit == null && tokenUnit != null) {
				tokensInSession.put(token, tokenUnit);
				unit = tokenUnit;
			}
		}
		return unit;
	}

	/**
	 * 清除过期Token
	 * 
	 * @param formsInSession
	 */
	private void clearExpireToken(Map<String, TokenUnit> formsInSession) {
		Iterator<Entry<String, TokenUnit>> entryIterator = formsInSession
				.entrySet().iterator();
		while (entryIterator.hasNext()) {
			TokenUnit tokenUnit = entryIterator.next().getValue();
			if (tokenUnit.finishFlag
					&& tokenUnit.expire < System.currentTimeMillis()) {
				if (logger.isDebugEnabled()) {
					logger.debug("清除缓存Token[" + tokenUnit.token + "]");
					entryIterator.remove();
				}
			}
		}
	}

	/**
	 * 判断给定的URL是否需要进行检查
	 */
	private boolean isSkipped(String currentUri) {

		for (String moduleUrl : blackExtensions) {
			// 判断当前url是否包括配置文件中的url
			if (currentUri.endsWith(moduleUrl)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 获取token
	 * 
	 * @param request
	 * @return
	 */
	private String getFormToken(HttpServletRequest request) {
		String formToken = request.getParameter(FORM_TOKEN_FIELD_NAME);
		if (formToken == null) {
			formToken = (String) request.getAttribute(FORM_TOKEN_FIELD_NAME);
		}
		return formToken;
	}

	/**
	 * Getter method for property <tt>blackExtensions</tt>.
	 * 
	 * @return property value of blackExtensions
	 */
	public List<String> getBlackExtensions() {
		return blackExtensions;
	}

	/**
	 * Setter method for property <tt>blackExtensions</tt>.
	 * 
	 * @param blackExtensions
	 *            value to be assigned to property blackExtensions
	 */
	public void setBlackExtensions(List<String> blackExtensions) {
		this.blackExtensions = blackExtensions;
	}

	/**
	 * Getter method for property <tt>timeout</tt>.
	 * 
	 * @return property value of timeout
	 */
	public int getTimeout() {
		return timeout;
	}

	/**
	 * Setter method for property <tt>timeout</tt>.
	 * 
	 * @param timeout
	 *            value to be assigned to property timeout
	 */
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	/**
	 * 防重复提交Token标识
	 * 
	 * @author Seer
	 * @version DuplicateSubmitTokenCheckInterceptor.java, v 0.1 2015年9月24日
	 *          下午4:05:45 Seer Exp.
	 */
	private class TokenUnit {
		/** Token */
		private String token;
		/** Token过期时间 */
		private long expire;
		/** 请求处理结束标识 */
		private boolean finishFlag;

		/**
         * 
         */
		public TokenUnit(String token, long expire, boolean finishFlag) {
			this.token = token;
			this.expire = expire;
			this.finishFlag = finishFlag;
		}

	}
}
